﻿
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight.Messaging;
using SessionHunter.Business.Exceptions;
using SessionHunter.DataAccess;
using SessionHunter.Messages;

namespace SessionHunter.ViewModel
{
    /// <summary>
    /// Allows the user to enter a new or change the existing connection string 
    /// </summary>
    public class ChangeDatabaseConnectionViewModel : ViewModelBase, IDataErrorInfo
    {
        private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();

        #region RelayCommands - Buttons
        /// <summary>
        /// To send a message to test the current connection
        /// </summary>
        public RelayCommand<PasswordBox> Command_TestConnection { get; private set; }
        /// <summary>
        /// To send a message to save the current connection and close the window
        /// </summary>
        public RelayCommand<PasswordBox> Command_Save { get; private set; }
        /// <summary>
        /// To send a message to cancel and close the window
        /// </summary>
        public RelayCommand Command_CancelOrExit { get; private set; }
        #endregion

        private bool _hasPassedValidation = false;
        private bool _dirty = false;

        /// <summary>
        /// Types of connections supported
        /// </summary>
        public enum ConnectionTypes
        {
            /// <summary>
            /// SQL Standard Authentication
            /// </summary>
            SQLAuthentication,
            /// <summary>
            /// Windows Integrated Authentication
            /// </summary>
            IntegratedAuthentication
        }

        /// <summary>
        /// Gets or sets the type of the connection currently selected.
        /// </summary>
        /// <value>
        /// The type of the connection.
        /// </value>
        public ConnectionTypes ConnectionType
        {
            get;
            set;
        }

        /// <summary>
        /// Initializes a new instance of the ReferencesViewModel class.
        /// </summary>
        public ChangeDatabaseConnectionViewModel()
        {
            Command_TestConnection = new RelayCommand<PasswordBox>(TestConnection);
            Command_Save = new RelayCommand<PasswordBox>(Save);
            Command_CancelOrExit = new RelayCommand(CancelOrExit);

            // Default to Integrated Auth, since it is more secure
            ConnectionType = ConnectionTypes.IntegratedAuthentication;

            // We should alway encrypt it by default, for added security
            _encryptConnection = true;

            // Load the connection string data if it exists
            string server = null;
            string database = null;
            bool isEncrypted = false;
            bool isIntegratedAuth = false;
            string userName = null;
            ViewModelLocator.MainStatic.ConnectionStringRepository.GetConnectionStringValues(out server, out database, out isEncrypted, out isIntegratedAuth, out userName);
            if (!String.IsNullOrEmpty(server))
            {
                _dataSource = server;
                _database = database;
                _encryptConnection = isEncrypted;
                if (isIntegratedAuth)
                {
                    ConnectionType = ConnectionTypes.IntegratedAuthentication;
                }
                else
                {
                    ConnectionType = ConnectionTypes.SQLAuthentication;
                    _username = userName;
                }
            }
        }

        private string _dataSource;

        /// <summary>
        /// Gets or sets the server and instance that will be put into the connection string.
        /// </summary>
        /// <value>
        /// The server.
        /// </value>
        public string DataSource
        {
            get
            {
                return _dataSource;
            }
            set
            {
                _dataSource = value;
                DataChanged();
                RaisePropertyChanged("Server");
            }
        }

        private string _database;

        /// <summary>
        /// Gets or sets the name of the database (aka Initial Catalog).
        /// </summary>
        /// <value>
        /// The database.
        /// </value>
        public string Database
        {
            get
            {
                return _database;
            }
            set
            {
                _database = value;
                DataChanged();
                RaisePropertyChanged("Database");
            }
        }


        private string _username;

        /// <summary>
        /// Gets or sets the username that will be put into the connection string.
        /// </summary>
        /// <value>
        /// The server.
        /// </value>
        public string Username
        {
            get
            {
                return _username;
            }
            set
            {
                _username = value;
                DataChanged();
                RaisePropertyChanged("Username");
            }
        }

        private bool _encryptConnection;

        /// <summary>
        /// Gets or sets a value indicating whether we should encrypt the connection we save or leave it clear text
        /// </summary>
        /// <value>
        ///   <c>true</c> if [encrypt connection]; otherwise, <c>false</c>.
        /// </value>
        public bool EncryptConnection
        {
            get { return _encryptConnection; }
            set { _encryptConnection = value; }
        }


        #region Connection Radio button bindings
        /// <summary>
        /// Value indicating whether Integrated Authentication is currently selected.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if [connection_ integrated authentication]; otherwise, <c>false</c>.
        /// </value>
        public bool Select_IntegratedAuthentication
        {
            get
            {
                return ConnectionType == ConnectionTypes.IntegratedAuthentication;
            }
            set
            {
                if (value)
                {
                    ConnectionType = ConnectionTypes.IntegratedAuthentication;
                    Username = null; // Clear out any existing username since we don't need it anymore
                    DataChanged();
                    RaisePropertyChanged("Connection_IntegratedAuthentication");
                }
            }
        }


        /// <summary>
        /// Value indicating whether SQL Authentication is currently selected.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if [connection_ SQL authentication]; otherwise, <c>false</c>.
        /// </value>
        public bool Select_SQLAuthentication
        {
            get
            {
                return ConnectionType == ConnectionTypes.SQLAuthentication;
            }
            set
            {
                if (value)
                {
                    ConnectionType = ConnectionTypes.SQLAuthentication;
                    DataChanged();
                    RaisePropertyChanged("Connection_SQLAuthentication");
                }
            }
        }
        #endregion


        /// <summary>
        /// Called whenever any of the data used in the connection string has changed
        /// </summary>
        private void DataChanged()
        {
            RaisePropertyChanged("IsSaveEnabled");
            _dirty = true;
            // Invalidate any previous validation
            _hasPassedValidation = false;
        }

        /// <summary>
        /// Whether it should be possible to save the connection string
        /// </summary>
        /// <returns>True if the button should be enabled</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1065:DoNotRaiseExceptionsInUnexpectedLocations")]
        public bool IsSaveEnabled
        {
            get
            {
                if (ConnectionType == ConnectionTypes.SQLAuthentication)
                    return !string.IsNullOrEmpty(_dataSource) && !string.IsNullOrEmpty(_database) && !string.IsNullOrEmpty(_username);
                else if (ConnectionType == ConnectionTypes.IntegratedAuthentication)
                    return !string.IsNullOrEmpty(_dataSource) && !string.IsNullOrEmpty(_database);
                else
                    throw new UnhandledCaseException(ConnectionType.ToString());
            }
        }

        /// <summary>
        /// Tests the connection to make sure it is valid.
        /// </summary>
        /// <param name="passwordBox">The password box.</param>
        private void TestConnection(PasswordBox passwordBox)
        {
            TestConnection(passwordBox, true);
        }

        /// <summary>
        /// Tests the connection to make sure it is valid.
        /// </summary>
        /// <param name="passwordBox">The password box.</param>
        /// <param name="showSuccessMessage">if set to true, shows the message to the client when it passes</param>
        private void TestConnection(PasswordBox passwordBox, bool showSuccessMessage)
        {
            var connectionString = ViewModelLocator.MainStatic.ConnectionStringRepository.CreateConnectionString(DataSource, Database, ConnectionType == ConnectionTypes.IntegratedAuthentication, Username, passwordBox.Password);
            var exceptionMessage = SessionRepository.TestConnection(connectionString);
            if (string.IsNullOrEmpty(exceptionMessage))
            {
                _hasPassedValidation = true;
                // See if we should show a message saying it passed
                if (showSuccessMessage)
                {
                    var message = new DialogMessage(Resources.Strings.TestConnection_Passed, null)
                    {
                        Button = MessageBoxButton.OK,
                        Caption = Resources.Strings.TestConnection_Passed_Dialog
                    };

                    Messenger.Default.Send(message);
                    _dirty = false;
                }
            }
            else
            {
                // Connection failed, show error message
                var message = new DialogMessage(exceptionMessage, null)
                {
                    Button = MessageBoxButton.OK,
                    Caption = Resources.Strings.TestConnection_Failed_Dialog
                };

                Messenger.Default.Send(message);
            }
        }

        /// <summary>
        /// Saves the connection string, using the password from the PasswordBox.
        /// </summary>
        /// <param name="passwordBox">The password box.</param>
        private void Save(PasswordBox passwordBox)
        {
            if (!_hasPassedValidation)
            {
                TestConnection(passwordBox, false);
            }

            if (_hasPassedValidation)
            {
                // Save the current connection string
                ViewModelLocator.MainStatic.ConnectionStringRepository.SaveConnectionStringValues(DataSource, Database, EncryptConnection, ConnectionType == ConnectionTypes.IntegratedAuthentication, Username, passwordBox.Password);

                // Close the window
                Messenger.Default.Send<ChangeDatabaseConnectionMessage>(new ChangeDatabaseConnectionMessage(ChangeDatabaseConnectionMessage.MessageAction.CloseChangeDatabaseConnectionDialog));

                // Force the refresh of any data
                Messenger.Default.Send(new MainViewModelMessage(MainViewModelMessage.MessageAction.ConnectionStringChanged));
            }
        }

        private void CancelOrExit()
        {
            // Close the window
            Messenger.Default.Send<ChangeDatabaseConnectionMessage>(new ChangeDatabaseConnectionMessage(ChangeDatabaseConnectionMessage.MessageAction.CloseChangeDatabaseConnectionDialog));
        }

        #region IDataErrorInfo Members

        /// <summary>
        /// Gets an error message indicating what is wrong with this object.
        /// </summary>
        /// <returns>An error message indicating what is wrong with this object. The default is an empty string ("").</returns>
        public string Error
        {
            get { return this[null]; }
        }

        /// <summary>
        /// Gets the error message for the property with the given name.  
        /// </summary>
        /// <returns>The error message for the property. The default is an empty string ("").</returns>
        /// <remarks>The propertyName parameter is the binding, not the control name.</remarks>
        public string this[string propertyName]
        {
            get
            {
                // don't bother validating unless something has actually changed
                if (!_dirty)
                    return null;

                string result = string.Empty;
                propertyName = propertyName ?? string.Empty;

                // Data Source
                if (propertyName == "DataSource")
                {
                    // 32 = Server name max of 15 + 1 for the slash + 16 for the SQL instance
                    if (string.IsNullOrEmpty(this.DataSource) || this.DataSource.Length > 32)
                    {
                        result += string.Format(Resources.Strings.Validation_NotEmpty_TooLong, "server", 32) + Environment.NewLine;
                    }
                }

                // Database
                if (propertyName == "Database")
                {
                    // 128 = Max database name length
                    if (string.IsNullOrEmpty(this.Database) || this.Database.Length > 128)
                    {
                        result += string.Format(Resources.Strings.Validation_NotEmpty_TooLong, "database", 128) + Environment.NewLine;
                    }
                }

                // Validation specific to SQL Auth
                if (ConnectionType == ConnectionTypes.SQLAuthentication)
                {
                    // Username
                    if (propertyName == "Username")
                    {
                        // 128 = max username size
                        if (string.IsNullOrEmpty(this.Username) || this.Username.Length > 128)
                        {
                            result += string.Format(Resources.Strings.Validation_NotEmpty_TooLong, "user name", 128) + Environment.NewLine;
                        }
                    }
                }

                return result.TrimEnd();
            }
        }

        #endregion
    }
}
